package org.kabanyo.primitives {

	import spark.primitives.Path;

	/**
	 * The CirclePath Class is a filled graphic element that draws a series of path segments and shapes an circle.
	 *
	 * @langversion 3.0
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
	 * @productversion Flex 4
	 *
	 * @author Desky Natalio
	 *
	 */
	public class CircularArc extends Path {
		private var _centerX:Number=55;

		[Bindable]
		public function get centerX():Number {
			return _centerX;
		}

		public function set centerX(value:Number):void {
			_centerX=value;
			invalidateData();
		}

		private var _centerY:Number=55;

		[Bindable]
		public function get centerY():Number {
			return _centerY;
		}

		public function set centerY(value:Number):void {
			_centerY=value;
			invalidateData();
		}

		private var _innerRadius:Number=50;

		[Bindable]
		public function get innerRadius():Number {
			return _innerRadius;
		}

		public function set innerRadius(value:Number):void {
			_innerRadius=value;
			invalidateData();
		}

		private var _thick:Number=5;

		[Bindable]
		public function get thick():Number {
			return _thick;
		}

		public function set thick(value:Number):void {
			_thick=value;
			invalidateData();
		}

		private var _startAngle:Number=0;

		[Bindable]
		public function get startAngle():Number {
			return _startAngle;
		}

		public function set startAngle(value:Number):void {
			_startAngle=value;
			var angle:Number=value % 360;
			if (angle < 0) {
				angle=360 - angle;
			} else if (angle == 0 && (value / 360) >= 1) {
				_startAngle=360;
			} else {
				_startAngle=angle;
			}
			invalidateData();
		}

		private var _endAngle:Number=360;

		[Bindable]
		public function get endAngle():Number {
			return _endAngle;
		}

		public function set endAngle(value:Number):void {
			_endAngle=value;
			var angle:Number=value % 360;
			if (angle < 0) {
				angle=360 - angle;
			} else if (angle == 0 && (value / 360) >= 1) {
				_endAngle=360;
			} else {
				_endAngle=angle;
			}
			invalidateData();
		}

		/**
		 * Constructor
		 *
		 * @langversion 3.0
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @productversion Flex 4
		 */
		public function CircularArc() {
			super();
			invalidateData();
		}

		private function invalidateData():void {
			const innerCurves:Array=createArc(innerRadius, startAngle, endAngle);
			const outterCurves:Array=createArc(innerRadius + thick, endAngle, startAngle);

			var firstData:String=null;
			var startPoint:StartPoint;
			for each (var innerCurve:Arc in innerCurves) {
				if (firstData == null) {
					startPoint.x=innerCurve.x1;
					startPoint.y=innerCurve.y1;
					firstData="M " + (innerCurve.x1 + centerX) + " " + (innerCurve.y1 + centerY);
				}
				firstData+=" C " + (innerCurve.x2 + centerX) + " " + (innerCurve.y2 + centerY) + " " + (innerCurve.x3 + centerX) + " " + (innerCurve.y3 + centerY) + " " + (innerCurve.x4 + centerX) + " " + (innerCurve.y4 + centerY);
			}
			var secondData:String=null;
			for each (var outterCurve:Arc in outterCurves) {
				if (secondData == null) {
					secondData="L " + (outterCurve.x1 + centerX) + " " + (outterCurve.y1 + centerY);
				}
				secondData+=" C " + (outterCurve.x2 + centerX) + " " + (outterCurve.y2 + centerY) + " " + (outterCurve.x3 + centerX) + " " + (outterCurve.y3 + centerY) + " " + (outterCurve.x4 + centerX) + " " + (outterCurve.y4 + centerY);
			}
			data=firstData + " " + secondData + " L " + (startPoint.x + centerX) + " " + (startPoint.y + centerY) + " Z";
		}

		/**
		 *  Return a array of objects that represent bezier curves which approximate the
		 *  circular arc centered at the origin, from startAngle to endAngle (radians) with
		 *  the specified radius.
		 *
		 *  Each bezier curve is an object with four points, where x1,y1 and
		 *  x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's
		 *  control points.
		 */
		public static function createArc(radius:Number, startAngle:Number, endAngle:Number):Array {
			const twoPI:Number=Math.PI * 2;
			startAngle=(startAngle / 360) * twoPI;
			endAngle=(endAngle / 360) * twoPI;

			// Compute the sequence of arc curves, up to PI/2 at a time.  Total arc angle
			// is less than 2PI.

			const curves:Array=[];
			const piOverTwo:Number=Math.PI / 2.0;
			const sign:int=(startAngle <= endAngle) ? 1 : -1;

			var a1:Number;
			var a2:Number;

			if (sign == 1) {
				a1=startAngle;
				do {
					a2=a1 + piOverTwo;
					if (a2 > endAngle) {
						a2=endAngle;
					}
					curves.push(createPartArc(radius, a1, a2));
					a1=a2;
				} while (a2 < endAngle);
			}
			if (sign == -1) {
				a1=startAngle;
				do {
					a2=a1 - piOverTwo;
					if (a2 < endAngle) {
						a2=endAngle;
					}
					curves.push(createPartArc(radius, a1, a2));
					a1=a2;
				} while (a2 > endAngle);
			}

			return curves;
		}

		/**
		 *  Cubic bezier approximation of a circular arc centered at the origin,
		 *  from (radians) a1 to a2, where a2-a1 < pi/2.  The arc's radius is r.
		 *
		 *  Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points
		 *  and x2,y2 and x3,y3 are the cubic bezier's control points.
		 *
		 *  This algorithm is based on the approach described in:
		 *  A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa,"
		 *  Information Technology and Control, 35(4), 2006 pp. 371-378.
		 */
		private static function createPartArc(r:Number, a1:Number, a2:Number):Object {

			const k:Number=0.5522847498;
			const a:Number=(a2 - a1) / 2.0;

			//Calculate Control Point
			const t:Number=Math.atan(k * Math.tan(a)); //Control Point Angle.
			const rc:Number=r / Math.cos(t); //Control Point Radius.

			var x1:Number=r * Math.cos(a1);
			var y1:Number=r * Math.sin(a1);
			var x2:Number=rc * Math.cos(a1 + t);
			var y2:Number=rc * Math.sin(a1 + t);
			var x3:Number=rc * Math.cos(a2 - t);
			var y3:Number=rc * Math.sin(a2 - t);
			var x4:Number=r * Math.cos(a2);
			var y4:Number=r * Math.sin(a2);

			return {x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3, x4: x4, y4: y4};
		}
	}
}

class Arc {
	public var x1:Number;
	public var y1:Number;
	public var x2:Number;
	public var y2:Number;
	public var x3:Number;
	public var y3:Number;
	public var x4:Number;
	public var y4:Number;
}

class StartPoint {
	public var x:Number;
	public var y:Number;
}
